//
//  IOMonad+Extensions.swift
//  iOSDemo
//
//  Created by FH on 2020/9/30.
//  Copyright © 2020 Fuhan. All rights reserved.
//

import Foundation

// REMARK: common functions

extension IOMonad {
    public static func just(_ val: T) -> IOMonad {
        return IOMonad { thunker in
            thunker.sendNext(val)
            thunker.sendComplete()
            return nil
        }.debugName("just")
    }
    
    public static func from<S: Sequence>(_ seq: S) -> IOMonad where S.Element == T {
        return IOMonad { thunker in
            for item in seq {
                thunker.sendNext(item)
            }
            thunker.sendComplete()
            return nil
        }.debugName("from")
    }
    
    public static func error(_ err: Error) -> IOMonad {
        return IOMonad { thunker in
            thunker.sendError(err)
            return nil
        }.debugName("error")
    }
    
    public static func empty() -> IOMonad {
        return IOMonad { thunker in
            thunker.sendComplete()
            return nil
        }.debugName("empty")
    }
}

public func just<T>(_ val: T) -> IOMonad<T> {
    return IOMonad<T>.just(val)
}

public func from<S: Sequence, T>(_ seq: S) -> IOMonad<T> where S.Element == T {
    return IOMonad<T>.from(seq)
}

public func error<T>(_ err: Error, type: T.Type) -> IOMonad<T> {
    return IOMonad<T>.error(err)
}

public func empty<T>(type: T.Type) -> IOMonad<T> {
    return IOMonad<T>.empty()
}

// REMARK: merge methods

public func mergeFrom<T1, T2>(_ stream1: IOMonad<T1>,
                              _ stream2: IOMonad<T2>) -> IOMonad<Union2<T1, T2>> {
    return IOMonad<IOMonad<Union2<T1, T2>>> { thunker in
        thunker.sendNext(stream1.map { Union2<T1, T2>.Value1($0) })
        thunker.sendNext(stream2.map { Union2<T1, T2>.Value2($0) })
        thunker.sendComplete()
        // 如果dispose了，则flattenMap包装下的stream val的dispose被compose在一起。而当前的thunker则会因为isDisposed忽略sendNext
        return nil
    }.flatten()
}
public func mergeFrom<T1, T2, T3>(_ stream1: IOMonad<T1>,
                                  _ stream2: IOMonad<T2>,
                                  _ stream3: IOMonad<T3>) -> IOMonad<Union3<T1, T2, T3>> {
    return IOMonad<IOMonad<Union3<T1, T2, T3>>> { thunker in
        thunker.sendNext(stream1.map { Union3<T1, T2, T3>.Value1($0) })
        thunker.sendNext(stream2.map { Union3<T1, T2, T3>.Value2($0) })
        thunker.sendNext(stream3.map { Union3<T1, T2, T3>.Value3($0) })
        thunker.sendComplete()

        return nil
    }.flatten()
}
public func mergeFrom<T1, T2, T3, T4>(_ stream1: IOMonad<T1>,
                                      _ stream2: IOMonad<T2>,
                                      _ stream3: IOMonad<T3>,
                                      _ stream4: IOMonad<T4>) -> IOMonad<Union4<T1, T2, T3, T4>> {
    return IOMonad<IOMonad<Union4<T1, T2, T3, T4>>> { thunker in
        thunker.sendNext(stream1.map { Union4<T1, T2, T3, T4>.Value1($0) })
        thunker.sendNext(stream2.map { Union4<T1, T2, T3, T4>.Value2($0) })
        thunker.sendNext(stream3.map { Union4<T1, T2, T3, T4>.Value3($0) })
        thunker.sendNext(stream4.map { Union4<T1, T2, T3, T4>.Value4($0) })
        thunker.sendComplete()

        return nil
    }.flatten()
}

// REMARK: combine methods

fileprivate func combineFrom<L, R>(_ lhs: IOMonad<L>, _ rhs: IOMonad<R>, latestMode: Bool = true) -> IOMonad<(L?, R?)> {
    let mutex = Mutex()
    return IOMonad<(L?, R?)> { thunker in
        let composeDisposer = IODisposer()
        var allDisposer: [IODisposer] = []
        // 左侧最近的一个值
        var latestLeftValue: L? = nil
        // 右侧最近的一个值
        var latestRightValue: R? = nil
        var leftCompleted = false
        var rightCompleted = false
        
        // 发送combine的数据
        let sendNext = {
            let succ = mutex.tryLock()
            if latestMode {
                if let latestLeftValue = latestLeftValue,
                   let latestRightValue = latestRightValue {
                    thunker.sendNext((latestLeftValue, latestRightValue))
                }
            } else {
                thunker.sendNext((latestLeftValue, latestRightValue))
            }
            if succ {
                mutex.unlock()
            }
        }
        // 发送combine的完成
        let sendComplete = {
            let succ = mutex.tryLock()
            if leftCompleted && rightCompleted {
                thunker.sendComplete()
            }
            if succ {
                mutex.unlock()
            }
        }
        
        // 订阅左侧Stream的数据
        let leftDisposer = lhs.enableValueSemantics().subscribe(nextFn: { val in
            let succ = mutex.tryLock()
            latestLeftValue = val
            if succ {
                mutex.unlock()
            }
            sendNext()
        }, completeFn: {
            let succ = mutex.tryLock()
            leftCompleted = true
            if succ {
                mutex.unlock()
            }
            sendComplete()
        }) { err in
            let succ = mutex.tryLock()
            thunker.sendError(err)
            if succ {
                mutex.unlock()
            }
        }
        var succ = mutex.tryLock()
        allDisposer.append(leftDisposer)
        if succ {
            mutex.unlock()
        }
        // 订阅右侧stream的数据
        let rightDisposer = rhs.enableValueSemantics().subscribe(nextFn: { val in
            let succ = mutex.tryLock()
            latestRightValue = val
            if succ {
                mutex.unlock()
            }
            sendNext()
        }, completeFn: {
            let succ = mutex.tryLock()
            rightCompleted = true
            if succ {
                mutex.unlock()
            }
            sendComplete()
        }) { err in
            let succ = mutex.tryLock()
            thunker.sendError(err)
            if succ {
                mutex.unlock()
            }
        }
        succ = mutex.tryLock()
        allDisposer.append(rightDisposer)
        if succ {
            mutex.unlock()
        }
        
        composeDisposer.setDispose {
            for disposer in allDisposer {
                disposer.dispose()
            }
        }
        return composeDisposer.dispose
    }
}

public func combineLatest<T1, T2>(_ stream1: IOMonad<T1>,
                                  _ stream2: IOMonad<T2>) -> IOMonad<(T1, T2)> {
    return combineFrom(stream1, stream2).map {
        return ($0.0!, $0.1!)
    }
}
public func combineLatest<T1, T2, T3>(_ stream1: IOMonad<T1>,
                                      _ stream2: IOMonad<T2>,
                                      _ stream3: IOMonad<T3>) -> IOMonad<(T1, T2, T3)> {
    return combineFrom(combineFrom(stream1, stream2), stream3).map {
        return ($0.0!.0!, $0.0!.1!, $0.1!)
    }
}
public func combineLatest<T1, T2, T3, T4>(_ stream1: IOMonad<T1>,
                                          _ stream2: IOMonad<T2>,
                                          _ stream3: IOMonad<T3>,
                                          _ stream4: IOMonad<T4>) -> IOMonad<(T1, T2, T3, T4)> {
    return combineFrom(combineFrom(combineFrom(stream1, stream2), stream3), stream4).map {
        return ($0.0!.0!.0!, $0.0!.0!.1!, $0.0!.1!, $0.1!)
    }
}

public func combine<T1, T2>(_ stream1: IOMonad<T1>,
                            _ stream2: IOMonad<T2>) -> IOMonad<(T1?, T2?)> {
    return combineFrom(stream1, stream2, latestMode: false)
}
public func combine<T1, T2, T3>(_ stream1: IOMonad<T1>,
                                _ stream2: IOMonad<T2>,
                                _ stream3: IOMonad<T3>) -> IOMonad<(T1?, T2?, T3?)> {
    return combineFrom(combineFrom(stream1, stream2, latestMode: false), stream3, latestMode: false).map {
        return ($0.0?.0, $0.0?.1, $0.1)
    }
}
public func combine<T1, T2, T3, T4>(_ stream1: IOMonad<T1>,
                                    _ stream2: IOMonad<T2>,
                                    _ stream3: IOMonad<T3>,
                                    _ stream4: IOMonad<T4>) -> IOMonad<(T1?, T2?, T3?, T4?)> {
    return combineFrom(combineFrom(combineFrom(stream1, stream2,
                                               latestMode: false),
                                   stream3, latestMode: false),
                       stream4, latestMode: false).map {
        return ($0.0?.0?.0, $0.0?.0?.1, $0.0?.1, $0.1)
    }
}

public func combineLatest(_ streamList: [IOMonad<Any>]) -> IOMonad<[Any]>? {
    if streamList.count == 0 {
        return nil
    } else if streamList.count == 1 {
        return streamList[0].map { val in
            return [val]
        }
    } else {
        let stream1 = streamList[0]
        let stream2 = streamList[1]
        var combineStream = combineFrom(stream1, stream2).map { (val1, val2) in
            return [val1!, val2!]
        }
        for stream in streamList {
            combineStream = combineFrom(combineStream, stream).map({ (vals, val2) in
                return [vals!, [val2!]].flatMap { $0 }
            })
        }
        return combineStream
    }
}

// REMARK: like rac_willDealloc

public final class DeallocObserver {
    private var onDealloc: Action = { }
    fileprivate var signal: AnyIOStream!
    
    fileprivate func setDeallocFn(deallocFn: @escaping Action) {
        self.onDealloc = deallocFn
    }
    
    deinit {
        self.onDealloc()
    }
}

public protocol Deinitable {
    var dealloc: DeallocObserver { get }
}

/*
 注意: 不能写Obj.rac_willDealloc.subscribeComplete { print(Obj) }，会导致循环引用
 */
public extension Deinitable {
    var rac_willDealloc: IOMonad<Self> {
        if self.dealloc.signal == nil {
            let subject = IOSubject<Self>()
            self.dealloc.setDeallocFn {
                subject.sendComplete()
            }
            self.dealloc.signal = subject.signal
        }
        return self.dealloc.signal as! IOMonad<Self>
    }
}

// REMARK: trace assign path of writable properties

protocol PropTracer {
    var tracePath: String! { get }
    var tracedEntity: EntityTracable? { get }
    func setName(propName: String)
    func setPrefixPath(prefixPath: String)
    func setPropUpdatedFn(_ updateFn: @escaping Action1<(tracePath: String, value: Any)>)
}

// TODO: 可以整合到PropPublisher中，然后通过EntityTracable来控制是否启用
@propertyWrapper
class PropTraced<Value> : PropTracer {
    var propName: String!
    var tracePath: String!
    var onPropDidUpdated: Action1<(String, Any)>!
    
    var tracedEntity: EntityTracable? {
        return self.wrappedValue as? EntityTracable
    }
    
    var wrappedValue: Value {
        didSet {
            if onPropDidUpdated != nil {
                onPropDidUpdated((self.tracePath!, self.wrappedValue))
            }
        }
    }
    
    init(wrappedValue value: Value) {
        self.wrappedValue = value
    }
    
    func setName(propName: String) {
        self.propName = propName
    }
    
    func setPrefixPath(prefixPath: String) {
        self.tracePath = "\(prefixPath)->\(self.propName!)"
    }
    
    func setPropUpdatedFn(_ updateFn: @escaping Action1<(tracePath: String, value: Any)>) {
        self.onPropDidUpdated = updateFn
    }
}

protocol EntityTracable {
    
}

extension EntityTracable {
    func trace() -> IOMonad<(type: EntityTracable.Type, tracePath: String, value: Any)> {
        let subject = IOSubject<(type: EntityTracable.Type, tracePath: String, value: Any)>()
        Self.parsePaths(entity: self, prefixPath: "\(Mirror(reflecting: self).subjectType)", signalSubject: subject)
        return subject.signal
    }
    
    private static func parsePaths(entity: EntityTracable,
                            prefixPath: String,
                            signalSubject: IOSubject<(type: EntityTracable.Type, tracePath: String, value: Any)>) {
        for prop in Mirror(reflecting: entity).children {
            if let propTraced = prop.value as? PropTracer, propTraced.tracePath == nil {
                var propName = prop.label ?? ""
                if propName.count > 0 {
                    propName = String(propName.suffix(propName.count - 1))
                }
                propTraced.setName(propName: propName)
                propTraced.setPrefixPath(prefixPath: prefixPath)
                let tracePath = propTraced.tracePath!
                let entityType = type(of: entity)
                // 因为无法对protocol做unowned/weak，所以只能传递类型或者用class wrapper
                propTraced.setPropUpdatedFn {it in
                    signalSubject.sendNext((entityType, it.tracePath, it.value))
                    // update new tracedEntity instance
                    if let subTracedEntity = it.value as? EntityTracable {
                        parsePaths(entity: subTracedEntity,
                                   prefixPath: tracePath,
                                   signalSubject: signalSubject)
                    }
                }
                
                if let tracedEntity = propTraced.tracedEntity {
                    parsePaths(entity: tracedEntity, prefixPath: propTraced.tracePath, signalSubject: signalSubject)
                }
            }
        }
    }
}

// REMARK: like RAC(subject, prop) = RACObserve(subject2, prop2)

@dynamicMemberLookup
class RACAssign<Subject> where Subject : AnyObject & Deinitable {
    let subject: Subject
    
    init(_ subject: Subject) {
        self.subject = subject
    }

    // val -> val
    subscript<Property>(dynamicMember keyPath: WritableKeyPath<Subject, Property>) -> IOMonad<Property> {
        get {
            fatalError("cannot read from this object.")
        }
        set(stream) {
            let instance = self.subject
            stream
                .take(until: self.subject.rac_willDealloc)
                .subscribeNext {[weak instance] val in
                    instance?[keyPath: keyPath] = val
            }
        }
    }

    // val -> Optional<val>
    subscript<Property>(dynamicMember keyPath: WritableKeyPath<Subject, Property?>) -> IOMonad<Property> {
        get {
            fatalError("cannot read from this object.")
        }
        set(stream) {
            let instance = self.subject
            stream
                .take(until: self.subject.rac_willDealloc)
                .subscribeNext {[weak instance] val in
                    instance?[keyPath: keyPath] = val
            }
        }
    }

    // Optional<val> -> val
    subscript<Property>(dynamicMember keyPath: WritableKeyPath<Subject, Property>) -> IOMonad<Property?> {
        get {
            fatalError("cannot read from this object.")
        }
        set(stream) {
            let instance = self.subject
            stream
                .take(until: self.subject.rac_willDealloc)
                .subscribeNext {[weak instance] val in
                    if let val = val {
                        instance?[keyPath: keyPath] = val
                    }
            }
        }
    }
}

// REMARK: like some functions in Combine Framework

class PropAdditional {
    private(set) var tracePath: String?
    
    var hasValue: Bool {
        if let tracePath = self.tracePath, tracePath.count > 0 {
            return true
        }
        return false
    }
    
    func setTracePath(path: String) {
        self.tracePath = path
    }
}

fileprivate protocol PropPublisher {
    var willChangeSignal: IOMonad<(String, Any, PropAdditional?)>? { get }
    var didChangeSignal: IOMonad<(String, Any, PropAdditional?)>? { get }
    
    func setPropName(name: String)
    func setDataUpdateFn(willUpdateFn: Action1<(String, Any, PropAdditional?)>?,
                         didUpdateFn: Action1<(String, Any, PropAdditional?)>?)
    func setChangeSignal(willChangeSignal: IOMonad<(String, Any, PropAdditional?)>?,
                         didChangeSignal: IOMonad<(String, Any, PropAdditional?)>?)
}

@propertyWrapper
class PropPublished<Value> : PropPublisher {
    var willChangeSignal: IOMonad<(String, Any, PropAdditional?)>?
    var didChangeSignal: IOMonad<(String, Any, PropAdditional?)>?
    private var onWillUpdate: Action1<(String, Any, PropAdditional?)>?
    private var onDidUpdate: Action1<(String, Any, PropAdditional?)>?
    private var propName: String = ""
    let propAdditional = PropAdditional()
    
    deinit {
        print(">>> dealloc \(self.propName)")
    }
    
    init(wrappedValue value: Value) {
        self.wrappedValue = value
    }
    
    var projectedValue: PropPublished<Value> {
        return self
    }

    var wrappedValue: Value {
        willSet {
            if let onWillUpdate = self.onWillUpdate {
                onWillUpdate((self.propName, newValue, propAdditional.hasValue ? propAdditional : nil))
            }
        }
        didSet {
            if let onDidUpdate = self.onDidUpdate {
                onDidUpdate((self.propName, wrappedValue, propAdditional.hasValue ? propAdditional : nil))
            }
        }
    }
    
    func setPropName(name: String) {
        if name.count > 0 {
            self.propName = String(name.suffix(name.count - 1))
        }
    }
    
    func setDataUpdateFn(willUpdateFn: Action1<(String, Any, PropAdditional?)>?,
                         didUpdateFn: Action1<(String, Any, PropAdditional?)>?) {
        if let willUpdateFn = willUpdateFn, self.onWillUpdate == nil {
            self.onWillUpdate = willUpdateFn
        }
        if let didUpdateFn = didUpdateFn, self.onDidUpdate == nil {
            self.onDidUpdate = didUpdateFn
        }
    }
    
    func setChangeSignal(willChangeSignal: IOMonad<(String, Any, PropAdditional?)>?,
                         didChangeSignal: IOMonad<(String, Any, PropAdditional?)>?) {
        if let willChangeSignal = willChangeSignal, self.willChangeSignal == nil {
            self.willChangeSignal = willChangeSignal
        }
        if let didChangeSignal = didChangeSignal, self.didChangeSignal == nil {
            self.didChangeSignal = didChangeSignal
        }
    }
}


protocol EntityObservable { }

extension EntityObservable {
    
    func objectWillChange() -> IOMonad<(String, Any, PropAdditional?)> {
        var subject: IOSubject<(String, Any, PropAdditional?)>!
        var firstPublisher: PropPublisher!
        let props = Mirror(reflecting: self).children
        
        for p in props {
            if let publisher = p.value as? PropPublisher {
                if firstPublisher == nil {
                    firstPublisher = publisher
                }
                // 查找第一个属性, @PropPublished的实例
                if firstPublisher.willChangeSignal == nil {
                    if subject == nil {
                        subject = IOSubject<(String, Any, PropAdditional?)>()
                    }
                    publisher.setPropName(name: p.label ?? "")
                    publisher.setDataUpdateFn(willUpdateFn: {
                        subject.sendNext($0)
                    }, didUpdateFn: nil)
                } else {
                    // 已经绑定过，直接返回
                    break
                }
            }
        }
        if firstPublisher.willChangeSignal == nil {
            subject.signal.enableValueSemantics()
            firstPublisher.setChangeSignal(willChangeSignal: subject.signal, didChangeSignal: nil)
        }
        return firstPublisher.willChangeSignal!
    }
    
    func objectDidChange() -> IOMonad<(String, Any, PropAdditional?)> {
        var subject: IOSubject<(String, Any, PropAdditional?)>! = nil
        var firstPublisher: PropPublisher! = nil
        let props = Mirror(reflecting: self).children
        
        for p in props {
            if let publisher = p.value as? PropPublisher {
                if firstPublisher == nil {
                    firstPublisher = publisher
                }
                // 查找第一个属性, @YCPublished的实例
                if firstPublisher.didChangeSignal == nil {
                    if subject == nil {
                        subject = IOSubject<(String, Any, PropAdditional?)>()
                    }
                    publisher.setPropName(name: p.label ?? "")
                    publisher.setDataUpdateFn(willUpdateFn: nil, didUpdateFn: {
                        subject.sendNext($0)
                    })
                } else {
                    // 已经绑定过，直接返回
                    break
                }
            }
        }
        if firstPublisher.didChangeSignal == nil {
            subject.signal.enableValueSemantics()
            firstPublisher.setChangeSignal(willChangeSignal: nil, didChangeSignal: subject.signal)
        }
        return firstPublisher.didChangeSignal!
    }
}

@propertyWrapper
class EnvContext<Value> where Value : EntityObservable {
    private var value: Value
    
    // TODO: implement
    
    init(wrapped value: Value) {
        self.value = value
    }
    
    var wrappedValue: Value {
        return self.value
    }
}
